Here is an implementation of the Towers-of-Hanoi game that I think would
be a useful example of the MVC is action. (BTW, did anyone outside the UK
get my earlier posting of the Montana game?). Please note that this software
is purely educational - I REALLY don't spend all my time hacking games.
I hope you find it useful.
Address: Dept. of Computing Science, University of Glasgow,
17 Lilybank Gardens, Glasgow, United Kingdom. G12 8QQ
"
Object subclass: #Disk
instanceVariableNames: 'size '
classVariableNames: ''
poolDictionaries: ''
category: 'Tower-of-Hanoi'!
Disk comment:
'My instances represent the circular disks that are used in the Towers-of-Hanoi game. Their only attribute is their size which is expressed as a natural number. In a game, each size is unique.'!
'My instances hold the state of play of a Tower-of-Hanoi game. Their instance variables hold the three towers used to hold the disks (as an array) and a counter recording how many disks are being used.'!
!TowersOfHanoi methodsFor: 'accessing'!
leftTower
^self towers at: 1!
middleTower
^self towers at: 2!
moves
^moves!
numberOfDisks
^numberOfDisks!
numberOfDisks: aNaturalNumber
"This method initializes the receiver to play
a game using the given number of disks."
numberOfDisks := aNaturalNumber.
self reset.!
resetMoveCounter
moves := 0.!
rightTower
^self towers at: 3!
towers
^towers!
towers: anArrayOfTowers
towers := anArrayOfTowers.!
usedAnotherMove
moves := moves + 1.! !
!TowersOfHanoi methodsFor: 'converting'!
indexOf: aTower
"Return the index of aTower in the towers array.
This is needed by my view to position towers in
the proper part of the display. If the index is zero
then return an error."
| index |
index := self towers indexOf: aTower.
index = 0 ifTrue: [self error: 'Unknown tower.'].
^index!
optimalNumberOfMoves
"The TowersOfHanoi game can always be completed
in (2**N)-1 moves where N is the number of disks."
^(2 raisedToInteger: self numberOfDisks) - 1! !
!TowersOfHanoi methodsFor: 'initialize-release'!
initialize
self towers: (Array new: 3).
1 to: self towers size do: [:k |
self towers at: k put: Tower new].!
reset
self leftTower loadDisks: self numberOfDisks.
self middleTower makeEmpty.
self rightTower makeEmpty.
self resetMoveCounter.
self changed: #reset.! !
!TowersOfHanoi methodsFor: 'moving'!
moveDiskFrom: source to: destination
"This is the primitive disk moving method. A disk
is removed from the source to the destination disk.
The method returns false if the operation fails and
true if it succeeds. The method also requests an
update in any view that has the receiver as a model."
'My instances represent the towers used in a Towers of Hanoi game. Each tower can hold zero of more Disks subject to the constraint that a larger disk cannot be placed on top of a smaller one.'!
!Tower methodsFor: 'accessing'!
disks
^disks!
disks: aStackOfDisks
disks := aStackOfDisks.!
size
"How many disks are on the receive?"
^self disks size! !
!Tower methodsFor: 'disc operations'!
addDisk: aDisk
"If it is valid to do so, place aDisk on the top of the receiver
otherwise report an error."
(self willAccept: aDisk) ifFalse: [
self error: 'Larger disks cannot be placed on smaller ones.'].
self disks addFirst: aDisk.!
loadDisks: numberOfDisks
"Initialize the receiver with the given number of
disks. The disks are guaranteed to be in the proper
order."
self initialize.
numberOfDisks to: 1 by: -1 do: [:size |
self addDisk: (Disk ofSize: size)].!
makeEmpty
"This method clears out the receiver's old disks if
it has any and resets the stack of disks to be empty."
self disks isNil ifFalse: [self release].
self initialize.!
removeDisk
"Remove the top-most disk from the receiver and return
it as the result of the method. If there are no disks on
the receiver then return nil."
self isEmpty ifTrue: [^nil].
^self disks removeFirst!
topDisk
"Return the top-most disk of the receiver if there is
one and nil otherwise. If the receiver is empty, return
nil. This operation does not affect the receiver."
self isEmpty ifTrue: [^nil].
^self disks first! !
!Tower methodsFor: 'initialize-release'!
initialize
self disks: OrderedCollection new.!
release
self disks do: [:disk | disk release].
self disks release.
disks := nil.! !
!Tower methodsFor: 'testing'!
isEmpty
"Are there any disks on the receiver tower?"
^self size = 0!
willAccept: aDisk
"Is it legal to place aDisk on the receiver? It is only legal
if the receiver is empty or its topDisk is larger than aDisk."
'My instances provide a graphical display of the state of a TowersOfHanoi game. When a disk is moved, its graphical representation will be moved using a simple graphical animation. '!
!TowersOfHanoiView methodsFor: 'accessing'!
baseLine
"Return the Y ordinate of the base of the towers."
^self insetDisplayBox bottom - 20.!
diskScale
"The displayed width of a disk is computed from its
actual size by multiplying it by this scaling factor."
^self class maxDiskWidth / self model numberOfDisks!
xPositionOfLeftTower
^self xPositionOfTower: self model leftTower!
xPositionOfMiddleTower
^self xPositionOfTower: self model middleTower!
xPositionOfRightTower
^self xPositionOfTower: self model rightTower!
xPositionOfTower: aTower
"This method returns the X-ordinate for the given tower.
This is expressed in display coordinates and denotes the centre